package fasilkom.antz;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import com.golden.gamedev.object.Sprite;
import com.golden.gamedev.object.SpriteGroup;
import com.golden.gamedev.object.sprite.AdvanceSprite;

import fasilkom.antz.algo.PathFindingNode;
import fasilkom.antz.background.TiledBackground;
import fasilkom.antz.sprite.FontSprite;


/**
 * Sprite karakter.
 *
 */
public class GameChar extends AdvanceSprite {

	// CONSTANT //

	public static final int DIR_RIGHT = 0;
	public static final int DIR_LEFT = 1;
	public static final int DIR_UP = 2;
	public static final int DIR_DOWN = 3;

	/** Karakter sedang diam berdiri. */
	public static final int STATE_STAND = 0;
	/** Karakter sedang berjalan. */
	public static final int STATE_WALK = 1;
	/** Karakter sedang menyerang. */
	public static final int STATE_ATTACK = 2;

	/** Karakter tak bisa dikontrol. */
	public static final int CONTROL_DISABLED = 0;
	/** Karakter di bawah kontrol. */
	public static final int CONTROL_ENABLED = 1;

	/** Kecepatan berjalan. */
	public static final double WALK_SPEED = 0.1;

	private static final long serialVersionUID = -4158386801348394082L;

	// PROPERTY //

	public static int controlRight = KeyEvent.VK_RIGHT;
	public static int controlLeft = KeyEvent.VK_LEFT;
	public static int controlUp = KeyEvent.VK_UP;
	public static int controlDown = KeyEvent.VK_DOWN;

	public Status status;
	public boolean isEnemy;
	private int controlState;

	private int tile;

	private int attackCount;

	//private AnimatedSprite attackEffect;

	private FontSprite charType;

	public LinkedList<String> actionList;

	//public boolean finishAttacking;


	private SpriteGroup player;

	// REFERENCE/VARIABLE //

	private Antz main;

	private PathFindingNode walkPath;
	private PathFindingNode walkPathNext;

	// CONSTRUCTOR //

	public GameChar(Antz main, int type , boolean isEnemy) {
		// TODO type menentukan jenis, gambar, kekuatan, dsb
//		super(main.getImages("assets/hero.png", 3, 4));
/*<<<<<<< .mine
		super(main.getImages("assets/antlion.png", 12, 4));
=======*/
		super();
//>>>>>>> .r74
		this.isEnemy = isEnemy;
		if(isEnemy){
			if(type == Status.TANK)
				setImages(main.getImages("assets/Enemy-tank.png", 1, 4));
			else if(type == Status.WARRIOR)
				setImages(main.getImages("assets/Enemy.png", 1, 4));
			else if(type == Status.ROGUE)
				setImages(main.getImages("assets/Enemy-rogue.png", 1, 4));
		}
		else {
			if(type == Status.TANK)
				setImages(main.getImages("assets/tank.png", 1, 4));
			else if(type == Status.WARRIOR)
				setImages(main.getImages("assets/warrior.png", 1, 4));
			else if(type == Status.Z)
				setImages(main.getImages("assets/hero2.png", 1, 4));
			else if(type == Status.ROGUE)
				setImages(main.getImages("assets/Hero.png", 1, 4));
		}

		this.main = main;
		this.status = new Status(type);
		this.actionList = new LinkedList<String>();
		/*this.charType = new FontSprite(status.getTypeString(),
				main.fontManager.getFont(main.getImages
				("assets/font-small.png", 13, 6),
				"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789   .,;:!?\"-/&`' "));*/

		/*attackEffect = new AnimatedSprite(main.getImages("assets/effect.png",16,1));
		attackEffect.setLoopAnim(false);*/

		//finishAttacking = true;

		setLoopAnim(true);
		setStatus(STATE_STAND);
		
		if(isEnemy)
			setDirection(DIR_RIGHT);
		else
			setDirection(DIR_DOWN);
	}

	// METHOD //

	@Override
	protected void animationChanged(int oldStat, int oldDir, int status,
			int direction) {
		switch (direction) {
		case DIR_RIGHT:
			setAnimationFrame(3,3);
			break;
		case DIR_LEFT:
			setAnimationFrame(1,1);
			break;
		case DIR_UP:
			setAnimationFrame(2, 2);
			break;
		case DIR_DOWN:
			setAnimationFrame(0, 0);
			break;
		}

		if (status == STATE_STAND) {
			setAnimate(false);
			setFrame(0);
		} else {
			setAnimate(true);
		}
	}

	@Override
	public void update(long elapsedTime) {
		super.update(elapsedTime);

		/*charType.setLocation(getX() - getStage().background.getX(),
				getY() - getStage().background.getY());*/

		// Lanjutkan walking path
		updateWalkPath(elapsedTime);
		//attackEffect.update(elapsedTime);

		/*if(attackEffect.getFrame() == attackEffect.getFinishAnimationFrame()){
			//System.out.println(attackEffect.getStartAnimationFrame() + " " + attackEffect.getFinishAnimationFrame());
			//finishAttacking = true;
			counter++;
		}

		if(counter == attackCount){
			attackEffect.setLoopAnim(false);
			counter = 0;
		}*/

		// Update Control (status, direction, speed)
		updateMouseControl(elapsedTime);
	}

	public boolean moveOneStep(int direction) {
		int tileWidth = getStage().background.getTileWidth();
		int tileHeight = getStage().background.getTileHeight();
		PathFindingNode node = null;

		switch (direction) {
		case DIR_DOWN:
			node = traceTo((int) getX(), (int) getY() + tileHeight);
			break;
		case DIR_LEFT:
			node = traceTo((int) getX() - tileWidth, (int) getY());
			break;
		case DIR_RIGHT:
			node = traceTo((int) getX() + tileWidth, (int) getY());
			break;
		case DIR_UP:
			node = traceTo((int) getX(), (int) getY() - tileHeight);
			break;
		}

		if (node != null) {
			setNextWalkPath(node);
			return true;
		}
		return false;
	}

	/**
	 * Update berdasarkan kondisi mouse sebagai kontrol. Method ini dipanggil
	 * oleh {@link #update(long)}.
	 */
	private void updateMouseControl(long elapsedTime) {
		if (main.click()) {
			if (isMouseOver()) {
				getStage().onCharClicked(this);
			}
		}
	}

	public boolean isMouseOver() {
		Stage scene = getStage();
		double mx = scene.getMouseX() + scene.background.getX() +
			scene.background.getClip().x;
		double my = scene.getMouseY() + scene.background.getY() +
			scene.background.getClip().y;
		return mx >= getX() && mx <= getX() + getWidth() &&
				my >= getY() && my <= getY() + getHeight() &&
				scene.background.getClip().contains(scene.getMouseX(), scene.getMouseY());
	}

	/**
	 * Lakukan path finding dari posisi karakter saat ini menuju target tertentu.
	 *
	 * Penggunaan method ini lebih disarankan karena posisi mouse tidak
	 * digunakan begitu saja, melainkan mempertimbangkan walk path yang ada
	 * sekarang juga.
	 * @param targetX koordinat-x yang dituju
	 * @param targetY koordinat-y yang dituju
	 * @param maxDistance manhattan distance maksimal dari posisi awal
	 * @return path finding node yang pertama
	 */
	private PathFindingNode traceTo(int targetX, int targetY, int maxDistance) {
		int startX = walkPath == null ? (int) getX() : walkPath.x;
		int startY = walkPath == null ? (int) getY() : walkPath.y;

		return ((Stage) main.getCurrentGame()).trace(startX, startY,
				targetX, targetY, maxDistance);
	}

	private PathFindingNode traceTo(int targetX, int targetY) {
		int startX = walkPath == null ? (int) getX() : walkPath.x;
		int startY = walkPath == null ? (int) getY() : walkPath.y;

		return ((Stage) main.getCurrentGame()).trace(startX, startY,
				targetX, targetY);
	}

	/**
	 * Mengembalikan {@link Stage} tempat karakter berada.
	 * @return
	 */
	private Stage getStage() {
		return (Stage) main.getCurrentGame();
	}

	/**
	 * Lakukan update perpindahan berdasarkan walk path.
	 * @param elapsedTime update's elapsed time
	 */
	private void updateWalkPath(long elapsedTime) {
		// masukkan yang di antrian jika yang sekarang tidak ada (selesai)
		if (walkPath == null && walkPathNext != null) {
			setWalkPath(walkPathNext);
			walkPathNext = null;
		}
		// jalankan yang sekarang harus berjalan
		if (walkPath != null) {
			if (moveTo(elapsedTime, walkPath.x, walkPath.y, WALK_SPEED)) {
				// telah sampai tujuan satu node, lanjut ke node berikut
				setWalkPath(walkPath.next);
				// kurangi energy
				status.energy -= status.energyUsed;
				if (status.energy <= 0) {
					status.energy = 0;
					setNextWalkPath(null);
					setWalkPath(null);
				}
			}
		}
	}

	/**
	 * Set walk path dan sesuaikan arah dan statusnya, tapi tidak immediately,
	 * melainkan melanjutkan apa yang ada sekarang terlebih dahulu.
	 * @param node walk path node selanjutnya
	 */
	public void setNextWalkPath(PathFindingNode node) {
		if (walkPath != null) {
			walkPath.next = null;
		}
		walkPathNext = node;
	}

	/**
	 * Set walk path (immediately) dan sesuaikan arah dan statusnya.
	 * @param node walk path node yang dipakai
	 */
	private void setWalkPath(PathFindingNode node) {
		walkPath = node;

		if (walkPath != null) {
			setStatus(STATE_WALK);

			if (getX() < walkPath.x)
				setDirection(DIR_RIGHT);
			else if (getX() > walkPath.x)
				setDirection(DIR_LEFT);
			else if (getY() < walkPath.y)
				setDirection(DIR_DOWN);
			else
				setDirection(DIR_UP);

		} else {
			setStatus(STATE_STAND);
		}
	}

	@Override
	public void render(Graphics2D g) {
		super.render(g);
//		charType.render(g);
		//attackEffect.render(g);

		TiledBackground background = ((Stage) main.getCurrentGame()).background;

		int x = (int) (getX() - background.getX());
		int y = (int) (getY() - background.getY());

		g.setColor(Color.RED);
		g.drawRect(x, y + getHeight() - 3, getWidth(), 3);
		g.fillRect(x, y + getHeight() - 3, (int) ((float) getWidth() *
				status.health / status.maxHealth), 3);

		g.setColor(Color.BLUE);
		g.drawRect(x, y + getHeight() + 2, getWidth(), 3);
		g.fillRect(x, y + getHeight() + 2, (int) ((float) getWidth() *
				status.energy / status.maxEnergy), 3);

		// Render type
		BufferedImage[] icons = main.getImages("assets/icon-types.png", 4, 1);
		g.drawImage(icons[status.type], x - 21, y + getHeight() - 10, 20, 20, null);
	}

	// GETTER/SETTER //

	/**
	 * Mengubah status kontrol terhadap karakter. Status didefinisikan oleh
	 * konstanta <code>CONTROL_*</code>.
	 */
	public void setControlState(int controlState) {
		this.controlState = controlState;
	}

	/**
	 * Status kontrol karakter saat ini, yakni yang didefinisikan oleh konstanta
	 * <code>CONTROL_</code>.
	 * @return
	 */
	public int getControlState() {
		return controlState;
	}

	/**
	 * Lakukan path finding, jika berhasil jalankan karakter. Jika tidak bisa,
	 * maka karakter akan berhenti walaupun saat ini sedang berjalan.
	 * @param x koordinat-x yang dituju
	 * @param y koordinat-y yang dituju
	 * @return <code>true</code> jika bisa pindah
	 */
	public boolean tryMoveTo(int x, int y) {
		// path finding maksimal sejauh kemampuan
		PathFindingNode node = traceTo(x, y, status.energy / status.energyUsed);
		setNextWalkPath(node);
		return node != null;
	}

	public boolean moveTo(int x, int y) {
		// path finding maksimal sejauh kemampuan
		PathFindingNode node = traceTo(x, y);
		setNextWalkPath(node);
		return node != null;
	}


	/*private int countPercentage(int now, int max){
		int result;

		double nowD = (double)now;
		double maxD = (double)max;
		double percent;

		percent = (nowD / maxD);

		result = (int)Math.floor(percent * 30);

		return result;
	}*/

	public void setAI(SpriteGroup player, SpriteGroup enemy){
		Sprite target;
		int energyTemp = status.maxEnergy;
		int healthTemp = 0;
		attackCount = 0;
		int totalDamage = 0;

		this.player = player;

		// kalau health kurang dari setengah
		if(status.health < status.maxHealth / 2 && getStage().enemyHealthPotion > 0) {
			actionList.add("HEAL");
			energyTemp -= 2*status.energyUsed;
		}

		// cari ada target yang dalam area serang
		target = inAttackArea(player, 0);

		if(target!=null)
			healthTemp = ((GameChar)target).status.health;

		// kalo gk ada cari lawan yang paling dekat
		if(target == null){
			target = getNearestPlayer(player,0);
			if(target!=null){
				actionList.add("MOVE " + getDestination(target));
				energyTemp -= (tile*status.energyUsed);
				healthTemp = ((GameChar)target).status.health;
			}
		}

		// kalo energy masih cukup buat nyerang maka serang
		while((energyTemp - (3*status.energyUsed)) > 0 && target!= null){
			actionList.add("ATTACK " +  target.toString());
			energyTemp -= 3*status.energyUsed;
			healthTemp -= status.damage;
			totalDamage += status.damage;
			attackCount++;

			// kalo lawan yang diserang bakal mati
			if(healthTemp <= 0){
				break;
				/*target = inAttackArea(player,totalDamage);

				// kalo ada lawan lain dalam area serangan , ganti target
				if(target!= null && target.isActive()) {
					healthTemp = ((GameChar)target).status.health;
					totalDamage = 0;
				}*/

			}
		}

		// kalo dah gk ada lawan di area , cari lawan lain yang paling deket
		/*if(energyTemp > 0 && healthTemp <= 0){
			target = getNearestPlayer(player, totalDamage);
			actionList.add("MOVE " + getDestination(target));
			energyTemp -= (tile*status.energyUsed);
			healthTemp = ((GameChar)target).status.health;
		}

		// kalo energy masih cukup buat nyerang maka serang
		while((energyTemp - (3*status.energyUsed)) > 0 && target!= null){
			actionList.add("ATTACK " +  target.toString());
			energyTemp -= 3*status.energyUsed;
			healthTemp -= status.damage;
			totalDamage += status.damage;

			// kalo lawan yang diserang bakal mati
			if(healthTemp <= 0){
				//break;
				target = inAttackArea(player,totalDamage);

				// kalo ada lawan lain dalam area serangan , ganti target
				if(target!= null && target.isActive()) {
					healthTemp = ((GameChar)target).status.health;
					totalDamage = 0;
				}

			}
		}*/

		// buat debuging isi commandnya ada apa aja
		/*LinkedList<String> temp = new LinkedList<String>(actionList);

		while(!temp.isEmpty()){
			System.out.println(status.type + " " +temp.poll());
		}

		System.out.println("");*/

		tile = 0;
	}

	public void excecuteAction(){
		String temp[];
		GameChar target;

		if(!actionList.isEmpty()){
			String action = actionList.peek();
			temp = action.split(" ");

			if(temp[0].equals("MOVE"))
				this.moveTo(Integer.parseInt(temp[1]),Integer.parseInt(temp[2]));
			else if(temp[0].equals("ATTACK")) {
				//finishAttacking = false;
				target = getTargetSprite(temp[1]);
				getStage().bsSound.play("assets/punch_or_whack_-Vladimir-403040765.mp3");
				setAttackDirection(target);
				getStage().animateAttack(target, getStage().attackEffect);
				target.status.health -= status.damage;
				status.energy -= 3*status.energyUsed;
			}
			else if(temp[0].equals("HEAL")){
				status.health += 50;
				status.energy -= 2*status.energyUsed;
				getStage().animateAttack(this, getStage().saveEffect);
				getStage().bsSound.play("assets/nambahdarah.mp3");
				getStage().enemyHealthPotion--;
				if(status.health > status.maxHealth){
					status.health = status.maxHealth;
				}
			}
		}
		else {
			System.out.println("kosong");
		}
	}

	public void checkFinishedAction(){
		String temp[];
		if(!actionList.isEmpty()){
			String action = actionList.peek();
			temp = action.split(" ");

			if(temp[0].equals("MOVE")){
				if(getX() == Integer.parseInt(temp[1]) && getY() == Integer.parseInt(temp[2]) ){
					actionList.remove();
				}
			}
			else if(temp[0].equals("ATTACK") || temp[0].equals("HEAL")) {
				actionList.remove();
			}

			if(status.energy == 0 && !actionList.isEmpty()){
				actionList.remove();
			}

		}
	}

	public Sprite getNearestPlayer(SpriteGroup player, int totalDamage){
		Sprite x = null;
		int minDistance = 999999;
		int minTemp = 0;

		for(Sprite i : player.getSprites()){
			if (i == null || !i.isActive() || ((GameChar)i).status.health < totalDamage || getDistance(i) > 300)
				continue;

			minTemp = countTileDistance(i);

			//if(minTemp > 10) continue;

			if(minTemp < minDistance && minTemp > 0){
				minDistance = minTemp;
				x = i;
			}
		}

		//System.out.println("Taken : " + minDistance);

		return x;
	}

	public int countTileDistance(Sprite target){
		int totalTile = 9999999;
		int tempTotal = 0;

		PathFindingNode node = traceTo((int)target.getX()-40 , (int)target.getY());
		while(node!= null){
			node = node.next;
			tempTotal++;
		}
		//System.out.println(tempTotal);

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			System.out.println("kiri");
			return totalTile;
		}

		tempTotal = 0;

		node = traceTo((int)target.getX()+40 , (int)target.getY());
		while(node!= null){
			node = node.next;
			tempTotal++;
		}
		//System.out.println(tempTotal);

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			System.out.println("kanan");
			return totalTile;
		}

		tempTotal = 0;

		node = traceTo((int)target.getX() , (int)target.getY()+40);
		while(node!= null){
			node = node.next;
			tempTotal++;
		}
		//System.out.println(tempTotal);

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			System.out.println("bawah");
			return totalTile;
		}

		tempTotal = 0;

		node = traceTo((int)target.getX(), (int)target.getY()-40);
		while(node!= null){
			node = node.next;
			tempTotal++;
		}
		//System.out.println(tempTotal);

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			System.out.println("atas");
		}

		//System.out.println("Final :" + totalTile);

		return totalTile;
	}

	public String getDestination(Sprite target){
		String destination = "";

		int totalTile = 99999999;
		int tempTotal = 0;

		PathFindingNode node = traceTo(((int)target.getX()-40) , (int)target.getY());
		while(node!= null){
			node = node.next;
			tempTotal++;
		}

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			destination = ((int)target.getX()-40) + " " + (int)target.getY();
		}

		tempTotal = 0;

		node = traceTo(((int)target.getX()+40) , (int)target.getY());
		while(node!= null){
			node = node.next;
			tempTotal++;
		}

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			destination = ((int)target.getX()+40) + " " + (int)target.getY();
		}

		tempTotal = 0;

		node = traceTo((int)target.getX() , ((int)target.getY()+40));
		while(node!= null){
			node = node.next;
			tempTotal++;
		}

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			destination = (int)target.getX() + " " + ((int)target.getY()+40);
		}

		tempTotal = 0;

		node = traceTo((int)target.getX(), (int)target.getY()-40);
		while(node!= null){
			node = node.next;
			tempTotal++;
		}

		if(tempTotal < totalTile && tempTotal != 0 && totalTile != 0){
			totalTile = tempTotal;
			destination = (int)target.getX() + " " + ((int)target.getY()-40);
		}

		tile = totalTile;

		//System.out.println(destination);

		return destination;
	}

	public GameChar inAttackArea(SpriteGroup group, int totalDamage){
		GameChar target = null;

		int minHealth = 99999;
		boolean first = true;

		int x = (int)getX();
		int y = (int)getY();
		//System.out.println(x + " " + y);

		for (Sprite i : group.getSprites()) {
			if (i == null || !i.isActive() || ((GameChar)i).status.health < totalDamage)
				continue;

			int xTemp = (int)i.getX() ;
			int yTemp = (int)i.getY() ;

			if((xTemp-40 == x && yTemp== y) || (xTemp+40 == x && yTemp==y) || (xTemp == x && yTemp+40==y) ||
					(xTemp == x && yTemp-40==y)){

				if(first){
					target= (GameChar)i;
					minHealth = target.status.health;
					first = false;
				}
				else {
					// misalnya dalam area ada yang health lebih kecil
					if(((GameChar)i).status.health < minHealth){
						minHealth = target.status.health;
						target = (GameChar)i;
						//System.out.println("cupu");
					}
				}
				//System.out.println("wkkkk");
			}
		}

		//System.out.println(target);

		return target;
	}

	private GameChar getTargetSprite(String id){
		GameChar target = null;

		for(Sprite i : this.player.getSprites()){
			if (i == null || !i.isActive())
				continue;

			if(i.toString().equals(id)){
				target = (GameChar)i;
				break;
			}
		}

		return target;
	}

	public void setAttackDirection(GameChar target){
		if(getX() - target.getX() == 40){
			setDirection(DIR_LEFT);
		}
		else if(getX() - target.getX() == -40){
			setDirection(DIR_RIGHT);
		}
		else if(getY() - target.getY() == 40){
			setDirection(DIR_UP);
		}
		else if(getY() - target.getY() == -40){
			setDirection(DIR_DOWN);
		}
	}

	/*private void animateAttack(GameChar target){
		attackEffect.setLocation(target.getX()- getStage().background.getX(), target.getY()- getStage().background.getY());
		//attackEffect.setLoopAnim(true);
		attackEffect.setAnimate(true);

	}*/
}
